/*							*/
/*  Indexed Sequential Access Method			*/
/*  Relational Database Systems, Inc.			*/
/*							*/
/*		Proprietary Data			*/
/*    This document contains proprietary information	*/
/* and trade secrets which are the property of		*/
/* Relational Database Systems, Inc. (RDSI). This	*/
/* document is submitted to recipient in confidence.	*/
/* The information contained herein may not be used,	*/
/* copied or disclosed in whole or in part except as	*/
/* permitted by written agreement signed by an officer	*/
/* of RDSI.						*/
/*							*/

/*
 * file and record lock management routines
 *
 * Relational Database Systems, Inc.
 * Roy Harrington	November 4, 1981
 *
 *	This module provides four different locking facilities
 * using one of five different locking methods.
 *
 * Lock Facilities:
 *    Internal System level:
 *	 lksemaphore() -- ISAM file protection
 *			Semaphore provides serialization of all
 *			C-ISAM file accesses to insure file integrity.
 *    External User level:
 *	 lkopenlock() -- Exclusive open of ISAM file.
 *			 no other process can even open ISAM file.
 *	 lkfile()    --  File level lock.
 *			 no other process can add, delete, or update
 *			 any record in the file.
 *	 lkrecord()  --  Record level lock.
 *			 no other process can delete or update a
 *			 record which is locked.
 *
 * Lock Methods:
 *	NOLOCK	    --	no locking at all (single user)
 *	ONIXLOCK    --	"locking" system call provided by
 *			ONIX, PLEXUS, XENIX, ZILOG (slight variation)
 *	CREATLOCK   --	Vanilla UNIX - file creation => semaphore
 *			".lok" file => record lock information
 *	MEMLOCK     --	record lock information kept in memory
 *			only one process will access ISAM file
 *	DRVRLOCK    --	special I/O driver provides locking mechanism
 */


#include "isdec.c"

#ifdef CREATLOCK
#define TIMEAGED 120		/* aged semaphore lock in seconds */
#endif

#ifndef NOLOCK			/* single-user no locking at all */

#ifdef CREATLOCK		/* CREATMEM => CREATLOCK or MEMLOCK */
#define CREATMEM
#endif
#ifdef MEMLOCK
#define CREATMEM
#endif

/*
 * lock whole isam file
 */

islock(isfd)

int isfd;
{
	if (isenter(isfd, -1) == -1) return(-1);
	VOID lkfile(LOCKCOND);
	return(isexit());
}

/*
 * unlock whole isam file
 */

isunlock(isfd)
int isfd;
{
	if (isenter(isfd, -1) == -1) return(-1);
	VOID lkfile(UNLOCK);
	return(isexit());
}


/*
 * semaphore locking -- system level lock
 *	provides serialization of C-ISAM calls
 *	must be called before accessing any files
 *	type:	UNLOCK     -- release semaphore
 *		LOCKUNCOND -- create semaphore
 */

/* ARGSUSED */
STATIC int lksemaphore(type)
int type;
{
#ifdef ONIXLOCK
	if (lseek(filep->f_idxfd, 0x200L, 0) != 0x200L)
	    {
	    iserrno = errno;
	    iserrio = IO_SEEK+IO_IDX;
	    return(-1);
	    }
	if (locking(filep->f_idxfd, type, 0x3FFF0000L) == -1)
	    {
	    iserrno = errno;
	    iserrio = IO_LOCK+IO_IDX;
	    return(-1);
	    }
#endif  /* ONIXLOCK */

#ifdef CREATLOCK
#ifdef SEMPHLOCK
	struct sembuf sop;

	sop.sem_num = 0;			/* setup semphore 	*/
	sop.sem_op  = (type == UNLOCK ? 1 : -1);
	sop.sem_flg = SEM_UNDO;

	if (semop(filep->f_semid, &sop, 1) == -1) /* do semaphore	*/
	    {
	    iserrno = errno;
	    iserrio = IO_LOCK+IO_IDX;
	    return(-1);
	    }
#else
	char fname[24];
	struct stat st;
	long dumtime, time();
	register int fd;

	mksemname(filep->f_device, filep->f_inum, fname);  /*new fileid*/
	if (type == UNLOCK)			/* unlock ?		*/
	    {
	    VOID unlink(fname);
	    }
	else
	    {
	    while ((fd = creat(fname, 0)) == -1) /* create lock file	*/
		{
		if ((errno != EEXIST && errno != EACCES) ||
			access("/isam", 07) == -1) /* access "/isam" ?	*/
		    {
		    iserrno = errno;
		    iserrio = IO_CREA+IO_SEM;
		    return(-1);
		    }
		if (stat(fname, &st) == 0 &&	/* aged semaphore ?	*/
			time(&dumtime)-st.st_ctime > TIMEAGED)
		    {
		    iserrno = EFLOCKED;		/* yes			*/
		    return(-1);
		    }
		sleep(1);
		}
	    VOID close(fd);
	    }
#endif /* SEMPHLOCK */
#endif  /* CREATLOCK */

#ifdef DRVRLOCK
	if (lkfd < 0)
	    if ((lkfd = open(LFILNAM, RWMODE)) < 0)	/* open lock device */
		{
		iserrno = ENOLOK;
		iserrio = IO_OPEN+IO_SEM;
		return(-1);
		}

	liocb.l_lock.l_id.l_idno	= filep->f_inum;
	liocb.l_lock.l_val.l_rec.l_off	= LUSRBUSY;
	liocb.l_lock.l_val.l_rec.l_len	= 0L;
	liocb.l_lock.l_flags		= 0;

	if (type == UNLOCK)
	    {
	    if (ioctl(lkfd, LIOCUNLK, &liocb) < 0)
		{	/* ioctl() == -1 is a hard error for "lk" driver */
drvrbad:	iserrno = errno;
		iserrio = IO_IOCTL;
		return(-1);
		}
	    }
	else
	    {
	    for (;;)
		{
		if (ioctl(lkfd, LIOCLOCK, &liocb) < 0)
		    goto drvrbad;
		if (liocb.l_err == LERROK)
		    break;			/* we have set busy lock */
		sleep(1);
		}
	    }
#endif /* DRVRLOCK */

	if (type == UNLOCK)
	    openp->o_flags &= ~OSLOCK;
	else openp->o_flags |= OSLOCK;
	return(0);
}

/*
 * exclusive lock isam file
 *	type:	UNLOCK     - unlock file (closing file)
 *		LOCKCOND   - lock file (ordinary open)
 *		ISEXCLLOCK - lock file (exclusive open)
 */

STATIC int lkopenlock(type)
int type;
{
#ifdef ONIXLOCK
	long lockbase, locksize;
	long lseek();

#ifndef ZILOG
	lockbase = 0x40000000L;		/* semaphores at end of file */
	locksize = 0x00000001L;
	if (type == ISEXCLLOCK || type == UNLOCK)
	    locksize = 0x00010000L;
	else lockbase += (unsigned)openp->o_procid;
#else  /* ZILOG */
	lockbase = 200;			/* semaphores in dictionary */
	locksize = 1;
	if (type == ISEXCLLOCK || type == UNLOCK)
	    locksize = 200;
tryzilog:
#endif /* ZILOG */

	if (lseek(filep->f_idxfd, lockbase, 0) != lockbase)
	    {
	    iserrno = errno;
	    iserrio = IO_SEEK+IO_IDX;
	    return(-1);
	    }
	if (locking(filep->f_idxfd,
		(type == UNLOCK)? UNLOCK: LOCKCOND, locksize) == -1)
	    {
#ifdef ZILOG
	    if (errno == EACCES && locksize == 1 && (lockbase += 2) < 400)
		goto tryzilog;			/* try other semaphores	*/
#endif /* ZILOG */
	    if (errno != EACCES)
		{
		iserrno = errno;
		iserrio = IO_LOCK+IO_IDX;
		}
	    else iserrno = EFLOCKED;
	    return(-1);				/* just return error	*/
	    }
	return(0);
#endif  /* ONIXLOCK */

#ifdef CREATMEM   /* => CREATLOCK or MEMLOCK */
	if (lkread()) return(-1);
	switch(type)
	    {
	    case UNLOCK:
		if (locks.l_nopens == -1)
		    locks.l_nopens = 0;
		else locks.l_nopens--;
		break;

	    case ISEXCLLOCK:
		if (locks.l_nopens != 0)
		    goto bad;
		locks.l_nopens = -1;
		break;

	    default:
		if (locks.l_nopens == -1)
		    goto bad;
		locks.l_nopens++;
		break;
	    }
	return(lkwrite());
bad:
	iserrno = EFLOCKED;
	return(-1);
#endif  /* CREATMEM => CREATLOCK or MEMLOCK */

#ifdef DRVRLOCK
	liocb.l_lock.l_id.l_idno	= filep->f_inum;
	liocb.l_lock.l_val.l_rec.l_off	= LUSRXOPN;
	liocb.l_lock.l_val.l_rec.l_len	= 0L;
	liocb.l_lock.l_flags		= 0;

	switch (type)
	    {
	    case UNLOCK:	/* attempt to remove exclusive open lock */
				/*   if we might have set it		 */
		if (openp->o_mode & ISEXCLLOCK)
		    if (ioctl(lkfd, LIOCUNLK, &liocb) < 0)
			goto realbad;
		break;

	    case ISEXCLLOCK:	/* attempt to set exclusive open lock,	  */
				/*   driver will check for ordinary opens */
		if (ioctl(lkfd, LIOCXOPN, &liocb) < 0)
		    goto realbad;
		if (liocb.l_err != LERROK)	/* couldn't set lock */
		    goto bad;
		break;

	    default:	/* ordinary open - honor locking protocol  */
			/*   by testing for an exclusive open lock */
		if (ioctl(lkfd, LIOCTEST, &liocb) < 0)
		    goto realbad;
		if (liocb.l_err == LERRXIST)	/* lock exists */
		    goto bad;
		break;
	    }
	return(0);
bad:
	if (!iserrno) iserrno = EFLOCKED;
	return(-1);
realbad:
	iserrno = errno;
	iserrio = IO_IOCTL;
	return(-1);
#endif /* DRVRLOCK */
}

/*
 * lock a record
 *	recnum:	record number
 *	type:	UNLOCK   - unlock record
 *		LOCKCOND - lock record, return if locked
 *		LOCKTEST - test for locked record
 */

STATIC int lkrecord(recnum, type)
long recnum;
register int type;
{
#ifdef ONIXLOCK
	long lockbase, locksize;
	long lseek();
	register int locktype;

	if ((openp->o_flags & OFLOCK) || (openp->o_mode & ISEXCLLOCK))
	    return(0);

#ifdef ZILOG
	locksize = filep->f_datsize;	/* zilog => lock actual records */
	lockbase = (recnum-1) * (locksize+1);
#else  /* ZILOG */
	lockbase = 0x40000000L;		/* onix => lock semaphores	*/
	lockbase += recnum;
	locksize = 0x00000001L;
#endif  /* ZILOG */

	if (lseek(filep->f_datfd, lockbase, 0) != lockbase)
	    {
	    iserrno = errno;
	    iserrio = IO_SEEK+IO_DAT;
	    return(-1);
	    }

	locktype = ((type == UNLOCK) ? UNLOCK : LOCKCOND);

	if (locking(filep->f_datfd, locktype, locksize) == -1)
	    {
	    if (errno != EACCES)
		{
		iserrno = errno;
		iserrio = IO_LOCK+IO_DAT;
		}
	    else iserrno = ELOCKED;
	    return(-1);				/* just return error	*/
	    }

	if (type == LOCKTEST)
	    VOID locking(filep->f_datfd, UNLOCK, locksize);
	return(0);
#endif   /* ONIXLOCK */

#ifdef DRVRLOCK
	register int lkcmd;

	if ((openp->o_flags & OFLOCK) || (openp->o_mode & ISEXCLLOCK))
	    return(0);

	/* FIX ME - variable length records not handled.
	 *	for now test for all lock & try to set lock for rec no
	 *	in LOCK case; for UNLOCK, try to remove rec lock.
	 */
	if (type == UNLOCK)
	    lkcmd = LIOCUNLK;
	else
	    {
	    liocb.l_lock.l_id.l_idno		= filep->f_inum;
	    liocb.l_lock.l_val.l_rec.l_off	= LUSRALLK;
	    liocb.l_lock.l_val.l_rec.l_len	= 0L;
	    liocb.l_lock.l_flags		= 0;
	    if (ioctl(lkfd, LIOCTEST, &liocb) < 0)
		goto realbad;
	    if (liocb.l_err == LERRXIST)
		goto bad;
	    lkcmd = (type == LOCKCOND) ? LIOCLOCK : LIOCTEST;
	    }
	liocb.l_lock.l_id.l_idno	= filep->f_inum;
	liocb.l_lock.l_val.l_rec.l_off	= recnum;
	liocb.l_lock.l_val.l_rec.l_len	= 0L;
	liocb.l_lock.l_flags		= 0;
	if (ioctl(lkfd, lkcmd, &liocb) < 0)
	    goto realbad;
	if (liocb.l_err != LERROK)
	    goto bad;
	return(0);
bad:
	iserrno = ELOCKED;
	return(-1);
realbad:
	iserrno = errno;
	iserrio = IO_IOCTL;
	return(-1);
#endif  /* DRVRLOCK */

#ifdef CREATMEM  /* => CREATLOCK or MEMLOCK */
	register int i;

	if ((openp->o_flags & OFLOCK) || (openp->o_mode & ISEXCLLOCK))
	    return(0);

	if (lkread() || locks.l_filelock) goto bad;

	for (i=0; i<NLOCKS; i++)
	    {					/* this does LOCKTEST	*/
	    if (locks.l_recnum[i] == recnum)
		{
		if (locks.l_procid[i] != openp->o_procid)
		    goto bad;
		else if (type == LOCKCOND)
		    return(0);			/* record already locked */
		break;
		}
	    }

	if (type == LOCKCOND)
	    {
	    for (i=0; i<NLOCKS; i++)		/* find empty lock slot	*/
		if (locks.l_procid[i] == 0) break;

	    if (i >= NLOCKS)
		goto bad;
	    locks.l_procid[i] = openp->o_procid;
	    locks.l_recnum[i] = recnum;		/* insert new lock	*/
	    if (lkwrite()) return(-1);
	    }
	return(0);
bad:
	if (!iserrno) iserrno = ELOCKED;
	return(-1);
#endif  /* CREATMEM => CREATLOCK or MEMLOCK */
}

/*
 * whole file lock
 *	type:	UNLOCK   -- unlock whole file
 *		LOCKCOND -- lock whole file, return if locked
 */

STATIC int lkfile(type)
int type;
{
#ifdef ONIXLOCK
	long lockbase, locksize;

	if (openp->o_mode & ISEXCLLOCK)		/* exclusive mode ?	*/
	    return(0);				/* yes, no locking	*/
#ifdef ZILOG
	lockbase = 0L;			/* zilog => lock actual records */
	locksize = 0L;
#else  /* ZILOG */
	lockbase = 0x40000000L;		/* onix => lock semaphores	*/
	locksize = 0x0FFFFFFFL;
#endif  /* ZILOG */

	if (lseek(filep->f_datfd, lockbase, 0) != lockbase)
	    {
	    iserrno = errno;
	    iserrio = IO_SEEK+IO_DAT;
	    return(-1);
	    }

	if (locking(filep->f_datfd, type, locksize) == -1)
	    {
	    if (errno != EACCES)
		{
		iserrno = errno;
		iserrio = IO_LOCK+IO_DAT;
		}
	    goto bad;
	    }
#endif /* ONIXLOCK */

#ifdef CREATMEM   /* => CREATLOCK or MEMLOCK */
	register int i;

	if (openp->o_mode & ISEXCLLOCK)		/* exclusive mode ?	*/
	    return(0);				/* yes, no locking	*/
	if (lkread()) return(-1);
	switch(type)
	    {
	    case UNLOCK:
		if (openp->o_flags & OFLOCK)
		    locks.l_filelock = 0;

		for (i=0; i<NLOCKS; i++)
		    if (locks.l_procid[i] == openp->o_procid)
			{
			locks.l_procid[i] = 0;
			locks.l_recnum[i] = 0L;
			}
		break;

	    case LOCKCOND:
		if (locks.l_filelock != 0 && !(openp->o_flags & OFLOCK))
		    goto bad;

		for (i=0; i<NLOCKS; i++)
		    if (locks.l_procid[i] != 0 &&
			    locks.l_procid[i] != openp->o_procid)
			goto bad;
		locks.l_filelock = -1;
		break;
	    }
	if (lkwrite()) return(-1);
#endif  /* CREATMEM => CREATLOCK or MEMLOCK */

#ifdef DRVRLOCK
	register int n;
	register int pid = openp->o_procid;

	if (openp->o_mode & ISEXCLLOCK)		/* exclusive mode ?	*/
	    return(0);				/* yes, no locking	*/
	if ((n = lklist()) == -1)   /* get list of locks for this file */
	    return(-1);
	switch (type)
	    {
	    case UNLOCK:	/* remove all locks for this id & process */
				/*   except busy & excl open locks	  */
		while (--n >= 0)
		    if (locks[n].l_pid == pid)
			if (locks[n].l_val.l_rec.l_len != 0L  ||
				locks[n].l_val.l_rec.l_off >= 0L)
			    {
			    liocb.l_lock = locks[n]; /* struct assignment */
			    if (ioctl(lkfd, LIOCUNLK, &liocb) < 0)
				goto realbad;
			    }
		break;

	    case LOCKCOND:	/* check for locks for this id by others */
				/*   and ??? locks by this proc ???	 */
				/*       except for busy and excl open	 */
				/* if none, set all lock		 */
				/* FIX ME - replace LUSRALLK by range	 */
		while (--n >= 0)
		    if (locks[n].l_pid != pid  ||
			    locks[n].l_val.l_rec.l_len != 0L  ||
			    locks[n].l_val.l_rec.l_off >= 0L)
			goto bad;
		liocb.l_lock.l_id.l_idno	= filep->f_inum;
		liocb.l_lock.l_val.l_rec.l_off	= LUSRALLK;
		liocb.l_lock.l_val.l_rec.l_len	= 0L;
		liocb.l_lock.l_flags		= 0;
		if (ioctl(lkfd, LIOCLOCK, &liocb) < 0)
		    {
realbad:	    iserrno = errno;
		    iserrio = IO_IOCTL;
		    return(-1);
		    }
		break;
	    }
#endif /* DRVRLOCK */

	if (type == UNLOCK)
	    openp->o_flags &= ~OFLOCK;
	else openp->o_flags |= OFLOCK;
	return(0);
bad:
	if (!iserrno) iserrno = ELOCKED;
	return(-1);
}

#ifdef CREATMEM /* => CREATLOCK or MEMLOCK */
/*
 * read lock file
 */

STATIC int lkread()
{
#ifdef CREATLOCK
	byfill(CASTCP &locks, sizeof(struct lock), 0);

	if (lseek(filep->f_lokfd, 0L, 0) != 0L)
	    {
	    iserrno = errno;
	    iserrio = IO_SEEK+IO_LOK;
	    return(-1);
	    }
	if (read(filep->f_lokfd, CASTCP &locks, sizeof(struct lock)) == -1)
	    {
	    iserrno = errno;
	    iserrio = IO_READ+IO_LOK;
	    return(-1);
	    }
#endif /* CREATLOCK */
	return(0);
}

/*
 * write lock file
 */

STATIC int lkwrite()
{
#ifdef CREATLOCK
	if (lseek(filep->f_lokfd, 0L, 0) != 0L)
	    {
	    iserrno = errno;
	    iserrio = IO_SEEK+IO_LOK;
	    return(-1);
	    }
	if (write(filep->f_lokfd, CASTCP &locks, sizeof(struct lock)) == -1)
	    {
	    iserrno = errno;
	    iserrio = IO_WRIT+IO_LOK;
	    return(-1);
	    }
#endif /* CREATLOCK */
	return(0);
}
#endif /* CREATMEM => CREATLOCK or MEMLOCK */


#ifdef DRVRLOCK
STATIC int lklist()
{
	liocb.l_lock.l_id.l_idno = filep->f_inum;
	liocb.l_lkp = &locks[0];
	liocb.l_cnt = NLOCKS;
	if (ioctl(lkfd, LIOCLIST, &liocb) < 0  ||  liocb.l_err != LERROK)
	    {
	    iserrno = errno;
	    iserrio = IO_IOCTL;
	    return(-1);
	    }
	return(liocb.l_cnt);
}
#endif /* DRVRLOCK */


#ifdef ZILOG
/* 
 * convert ONIX locking to ZILOG locking
 */

STATIC int locking(fd, type, size)
int fd, type;
long size;
{
	struct lockblk
	    {
	    long lklen;
	    long lkoff;
	    int  lkwhnce;
	    } lkblk;
	long cc, unlk(), lkdata();

	lkblk.lklen = size;
	lkblk.lkoff = 0L;
	lkblk.lkwhnce = 0;

	switch(type)
	    {
	    case UNLOCK:
		cc = unlk(fd, 4, &lkblk);
		break;
	    case LOCKUNCOND:
		cc = lkdata(fd, 1, &lkblk);
		break;
	    case LOCKCOND:
		cc = lkdata(fd, 4, &lkblk);
		break;
	    }
	return(cc == -1L ? -1 : 0);
}
#endif  /* ZILOG */

#ifdef SU
locking()
{
    return(0);
}
#endif

#endif  /* NOLOCK */
